package com.teach.util;

import com.teach.commons.JsonUtil;
import com.teach.config.AuthConfigProperies;
import com.teach.dto.AuthUser;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <pre>
 * TODO : jwt 工具类
 * ClassName : com.teach.config.JwtUtil
 * Author : J.L.Zhou
 * Date : 2020-03-04 13:17
 * Version : V1.0
 * Copyright 2020 jlzhou.top Inc. All rights reserved. 
 * Warning: this content is only for internal circulation of the company. It is forbidden to divulge it or use it for other commercial purposes</pre>
 */
@Slf4j
@Component
public class JwtUtil {

    @Autowired
    private AuthConfigProperies config;
    
    @Autowired
    private JsonUtil jsonUtil;

    /**
     * TODO 创建jwt
     * @param id	是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
     * @param issuer 签发人
     * @param user 授权用户对象，实际转换为json字符串存放在jwt的subject中
     * @return
     * @throws Exception
     */
    public String createJWT(String id, String issuer, AuthUser user) throws Exception {
    	log.debug("创建JWT：id={} ,issuer={} ,user={}",id,issuer,user);
        // 指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        Date now = new Date();

        // 创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        // 创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        Map<String, Object> claims = new HashMap<>();
        claims.put("uid", "123456");

        // 生成签名的时候使用的秘钥secret，切记这个秘钥不能外露哦。它就是你服务端的私钥，在任何场景都不应该流露出去。
        // 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        SecretKey key = config.jwt.getSecretKey();

        // 下面就是在为payload添加各种标准声明和私有声明了
        JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder，设置jwt的body
                .setClaims(claims)          // 如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setId(id)                  // 设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
                .setIssuedAt(now)           // iat: jwt的签发时间
                .setIssuer(issuer)          // issuer：jwt签发人
                .setSubject(jsonUtil.toString(user))        // sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
                .signWith(signatureAlgorithm, key); // 设置签名使用的签名算法和签名使用的秘钥

        // 设置过期时间
        if (config.jwt.expiration >= 0) {
            long expMillis = now.getTime() + config.jwt.expiration;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }
        String ret = builder.compact();
        log.debug("成功创建JWT：{}",ret);
        return ret;
    }

    /**
     * TODO 解密jwt
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public Claims parseJWT(String jwt) throws Exception {
    	log.debug("正在解密JWT：{}",jwt);
        SecretKey key = config.jwt.getSecretKey();  //签名秘钥，和生成的签名的秘钥一模一样
        Claims claims = Jwts.parser()  //得到DefaultJwtParser
                .setSigningKey(key)                 //设置签名的秘钥
                .parseClaimsJws(jwt).getBody();     //设置需要解析的jwt
        return claims;
    }


    /**
     * TODO 获取授权用户信息
     * @param claims 解密后的有效载荷
     * @return
     * @throws Exception
     */
    public AuthUser getAuthUser(Claims claims) throws Exception {
    	//可以根据需要进一步的对有效载荷进行校验,和业务鉴权处理
        String subject = claims.getSubject();
        return jsonUtil.toObject(subject, AuthUser.class);
    }

}
